<template>
  <div ref="canvas"></div>
</template>

<script lang="ts" setup>
import * as THREE from "three";
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";
import Stats from "three/examples/jsm/libs/stats.module";
import {GUI} from "three/examples/jsm/libs/lil-gui.module.min.js";
import {onMounted, ref} from "vue";
// 引入扩展库GLTFLoader.js
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader"
import {OBJLoader} from 'three/addons/loaders/OBJLoader.js';


// 画布
const canvas = ref<any>('');
// 场景
const scene = new THREE.Scene();
// 相机
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000);
camera.position.set(-20, 50, 40);
camera.lookAt(0, 0, 0);
// 渲染器
const webGLRenderer = new THREE.WebGLRenderer({  antialias: true });
webGLRenderer.setClearColor(0x000000);
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.setPixelRatio(window.devicePixelRatio);
webGLRenderer.shadowMap.enabled = true;
webGLRenderer.shadowMap.type = THREE.PCFSoftShadowMap;
// 相机控件
const orbitControl = new OrbitControls(camera, webGLRenderer.domElement);
// 性能统计
const stat = Stats();
// ground
const groundGeometry = new THREE.PlaneGeometry(100, 100, 4, 4);
const groundMaterial = new THREE.MeshBasicMaterial({
  color: 0x777777
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -20;
scene.add(ground);
// sphere geometry
const sphereGeometry = new THREE.SphereGeometry(14, 20, 20);
// cube geometry
const cubeGeometry = new THREE.BoxGeometry(15, 15, 15);
// plane geometry
const planeGeometry = new THREE.PlaneGeometry(14, 14, 4, 4);

const material = new THREE.MeshBasicMaterial({
  color: 0x7777ff,
  name: 'Basic Material',
});

// sphere
const sphere = new THREE.Mesh(sphereGeometry, material);
sphere.position.set(0,3, 2);
// cube
const cube = new THREE.Mesh(cubeGeometry, material);
cube.position.set(0,3, 2);
// plane
const plane = new THREE.Mesh(planeGeometry, material);
plane.position.set(0,3, 2);

scene.add(cube);

// ambient light
const ambient = new THREE.AmbientLight(0x0c0c0c);
scene.add(ambient);
// spot light
const spot = new THREE.SpotLight(0xffffff);
spot.position.set(-40, 60, -10);
spot.castShadow = true;
scene.add(spot);

// gui
const step = 0;
const controlObj = {
  rotationSpeed: 0.02,
  bouncingSpeed: 0.03,
  color: material.color.getStyle(),
  selectedMesh: "cube",
};
let selectedMesh = cube;
const gui = new GUI();
const folder = gui.addFolder("THREE.MeshBasicMaterial");
folder.addColor(material, 'color').onChange(function (e) {
  material.color.setStyle(e)
});
// @ts-ignore
folder.add(material, 'wireframe');
// @ts-ignore
folder.add(material, 'wireframeLinewidth', 0, 20);
// @ts-ignore
folder.add(material, 'wireframeLinejoin', ['round', 'bevel', 'miter']).onChange( (e) => {
  material.wireframeLinejoin = e
});
// @ts-ignore
folder.add(material, 'wireframeLinecap', ['butt', 'round', 'square']).onChange( (e) => {
  material.wireframeLinecap = e
});

loadGopher(material).then((gopher) => {
  gopher.scale.x = 4;
  gopher.scale.y = 4;
  gopher.scale.z = 4;
  gui.add(controlObj, 'selectedMesh', ["cube", "sphere", "plane", "gopher"]).onChange( (e) => {

    scene.remove(plane);
    scene.remove(cube);
    scene.remove(sphere);
    scene.remove(gopher);

    switch (e) {
      case "cube":
        scene.add(cube);
        selectedMesh = cube;
        break;
      case "sphere":
        scene.add(sphere);
        selectedMesh = sphere;
        break;
      case "plane":
        scene.add(plane);
        selectedMesh = plane;
        break;
      case "gopher":
        scene.add(gopher);
        selectedMesh = gopher;
        break;
    }
  });
})

const render = () => {
  stat.update();
  orbitControl.update();

  requestAnimationFrame(render)
  webGLRenderer.render(scene, camera);
}

onMounted(() => {
  canvas.value.appendChild(stat.domElement);
  canvas.value.appendChild(webGLRenderer.domElement);

  render();
})

function loadGopher(material) {
  const loader = new GLTFLoader();
  let mesh = null;
  return new Promise( (resolve) => {
    loader.load(new URL("@/assets/gopher/gopher.obj", import.meta.url).href, function (loadedMesh) {
      // this is a group of meshes, so iterate until we reach a THREE.Mesh
      mesh = loadedMesh;
      if (material) {
        // material is defined, so overwrite the default material.
        computeNormalsGroup(mesh);
        setMaterialGroup(material, mesh);
      }
      resolve(mesh);
    });
  });
}

function setMaterialGroup(material, group) {
  if (group instanceof THREE.Mesh) {
    group.material = material;
  } else if (group instanceof THREE.Group) {
    group.children.forEach(function(child) {setMaterialGroup(material, child)});
  }
}

function computeNormalsGroup(group) {
  if (group instanceof THREE.Mesh) {
    const tempGeom = new THREE.BufferGeometry();
    tempGeom.setAttribute(group.geometry.getAttribute(), group.geometry.getIndex());
    // tempGeom.fromBufferGeometry(group.geometry)
    tempGeom.computeVertexNormals();
    // tempGeom.computeFaceNormals();
    tempGeom.normalizeNormals();
    // tempGeom.mergeVertices();
    tempGeom.computeBoundingBox();
    tempGeom.computeBoundingSphere();
    tempGeom.computeTangents();
    // tempGeom.normalsNeedUpdate = true;

    group.geometry = tempGeom;
  } else if (group instanceof THREE.Group) {
    group.children.forEach(function(child) {computeNormalsGroup(child)});
  }
}

</script>